#include "NxPhysics.h"
#include "NxControllerManager.h"
#include "NxController.h"
#include "Engine.h"
#include "Windows/GxWinApp.h"
#include "Windows/GxWinForm.h"
#include "LibGL/OpenGL.h"
#include "Basic/FileSystem.h"
#include "LibGL/GxGLCamera.h"
#include "LibGL/GxUI.h"
#include "Cubemap.h"
#include "Lightmap.h"
#include "PhysicsScene.h"

using namespace GxLibBasic;
using namespace GxWinGUI;
using namespace GxRendering::UI;

namespace FinalRendering
{
	namespace BspEditor
	{
		const float GravityAcceleration = 64.0f;

		const float CameraMinSpeed = 1.0f;
		const float CameraMaxSpeed = 1000.0f;
		const float CameraAcceleration = 400.0f;
		const float CameraTurnAngle = 3.14159f/4.0f;

		using namespace GxRendering;
		using namespace FinalRendering::Engine;

		class BspForm : public GLForm
		{
		private:
			RefPtr<Scene> scene;
			Camera cam;
			RefPtr<Renderer> renderer;
			RefPtr<UIEntry> ui;
			Label * lblNode;
			RefPtr<Cubemap> cubemap;
			RefPtr<Material> matCubeTest;
			RefPtr<ProgramPool> program;
			LightmapSolution * solution;
			RefPtr<FinalRendering::Engine::Engine> engine;
			RefPtr<FinalRendering::Engine::Physics::PhysScene> physScene;
			float fps;
			bool fullScreen;
		public:
			BspForm::BspForm()
			{
				bool succ = InitEngine();
				if (!succ)
				{
					MessageBox(L"Engine initialization failed. OpenGL 3.2 is not supported.",
						L"Error", MB_ICONEXCLAMATION);
					exit(0);
				}
				try
				{
					fullScreen = true;
					String cmd = Application::GetCommandLine();
					CommandLineParser parser(cmd);
					if (parser.OptionExists(L"-windowed"))
					{
						fullScreen = false;
					}
					solution = 0;
					OnKeyPressed.Bind(this, &BspForm::KeyPressed);
					
					engine = new FinalRendering::Engine::Engine();

					Global::Init();
					
					_wchdir(Global->GetWorkingDirectory().GetStrPtr());
					String fileName = parser.GetFileName();
					if (fileName.GetLength() && fileName[0] == L'\"')
					{
						fileName = fileName.Mid(1, fileName.GetLength()-2);
					}
					SetText(L"FinalRendering BSP Editor (Press L/R to generate lightmaps)");
					SetBorder(fbSingle);
					SetMaximizeBox(false);
					if (fullScreen)
					{
						SetLeft(0);
						SetTop(0);
						SetBorder(fbNone);
						SetClientWidth(SystemMetrics::GetScreenWidth());
						SetClientHeight(SystemMetrics::GetScreenHeight());
					}
					else
					{
						SetClientWidth(640);
						SetClientHeight(480);
						SetLeft((SystemMetrics::GetScreenWidth()-GetWidth())/2);
						SetTop((SystemMetrics::GetScreenHeight()-GetHeight())/2);
					}
					ui = new UIEntry(GetClientWidth(), GetClientHeight());
					lblNode = new Label();
					lblNode->Posit(20, GetClientHeight()-50,300,20);
					lblNode->DropShadow = true;
					lblNode->FontColor = GxRendering::UI::Color(255,255,255,255);
					ui->AddChild(lblNode);
					ui->BackColor.A = 0;
					renderer = new Renderer(GetClientWidth(), GetClientHeight());
					OpenGL::SetSwapInterval(0);
					
					LoadScene(fileName);
					renderer->WireFrame = false;
					cubemap = new Cubemap(256, Cubemap::Color);
					program = new ProgramPool();
					MaterialFile matFile(L"Materials/CubemapTest.gmt");
					matCubeTest = new Material(matFile, program.operator->());
					matCubeTest->OnSetClientUniform.Bind(this, &BspForm::matCubeTestUniform);
					Init();
					DebugLightmap = 0;
					DrawLightmap = false;
					DrawPortals = false;
				}
				catch (gxFileSystemException ex)
				{
					wchar_t cdir[500];
					GetCurrentDirectory(500, cdir);
					MessageBox(String(L"Error accessing file \"") + ex.Text + L"\".\nCurrent working directory is \"" +
						cdir + L"\"", L"Error", MB_ICONEXCLAMATION);
					exit(0);
				}
				catch (gxIndexOutOfRangeException ex)
				{
					MessageBox(ex.Text, L"Error", MB_ICONEXCLAMATION);
					exit(0);
				}
				catch (Exception ex)
				{
					MessageBox(String(L"Error: \"") + ex.Text + L"\"", L"Error", MB_ICONEXCLAMATION);
					exit(0);
				}
				
				catch (const wchar_t * ex)
				{
					MessageBox(String(L"Error: \"") + ex + L"\"", L"Error", MB_ICONEXCLAMATION);
					exit(0);
				}
				catch (...)
				{
					MessageBox(String(L"Unkown error."), L"Error", MB_ICONEXCLAMATION);
					exit(0);
				}
			}

			BspForm::~BspForm()
			{
				scene = 0;
				physScene = 0;
				renderer = 0;
				engine = 0;
				Global::Finalize();
				FinalizeEngine();
			}

			bool DrawLightmap;

			void RadiosityPassFinished(float converge)
			{
				SetText(String(L"Generating Radiosity... (Converged: ") + String((1.0f-converge)*100,1) + L"%)");
			}

			void GenLightmaps(bool rad)
			{
				solution = new LightmapSolution(scene.operator ->());
				solution->ComputeRadiosity = rad;
				solution->PatchSize = 64.0f;
				solution->RadiosityThreshold = 0.05f;
				solution->PassFinished.Bind(this, &BspForm::RadiosityPassFinished);
				solution->BindDiffuseTexture.Bind(this, &BspForm::BindDiffuseTexture);
				solution->Generate();
				renderer->LoadLightmapSolution(solution);
			}

			void KeyPressed(Object * sender, GxWinGUI::KeyEventArgs e)
			{
				if (e.Key == L'L' || e.Key == L'l' || e.Key == L'R' || e.Key == L'r')
				{
					if (!solution)
					{
						int id = GetTickCount();
						SetText(L"Generating Lightmaps...");
						bool rad = (e.Key == L'R' || e.Key == L'r');
						GenLightmaps(rad);
						id = GetTickCount()-id;
						SetText(String(L"FinalRendering BSP Editor (Lightmap Generated in ")
							+ String(id) + L" ms)");
					}
				}
				if (e.Key == L'P' || e.Key == L'p')
				{
					renderer->PvsCulling = !renderer->PvsCulling;
				}
				if (e.Key == L'Z' || e.Key == L'z')
					DebugLightmap ++;
				if (e.Key == L'X' || e.Key == L'x')
					DebugLightmap --;
				if (e.Key == L'M' || e.Key == L'm')
					DrawLightmap = !DrawLightmap;
				if (e.Key == L'V' || e.Key == L'v')
					DrawPortals = !DrawPortals;

				if (solution)
				{
					if (DebugLightmap < 0)
						DebugLightmap = 0;
					if (DebugLightmap > solution->GetMapCount()-1)
						DebugLightmap = solution->GetMapCount()-1;
				}
			}

			void Init()
			{
				glViewport(0,0, GetClientWidth(), GetClientHeight());
				glMatrixMode(GL_PROJECTION);
					glLoadIdentity();
					gluPerspective(60.0f,GetClientWidth()/(float)GetClientHeight(),1,10000.0f);
				glMatrixMode(GL_MODELVIEW);
				glEnable(GL_DEPTH_TEST);
				glEnable(GL_CULL_FACE);

				cam.pos = vec3(0.0f,48.0f,0.0f);
				
			}

			void BindDiffuseTexture(int id)
			{
				Material * mat = renderer->GetMaterial(id);
				if (mat)
				{

					GxRendering::Texture * tex = mat->GetDiffuseTexture();
					if (tex)
					{
						glEnable(GL_TEXTURE_2D);
						tex->Bind();
						return;
					}
				}
				glDisable(GL_TEXTURE_2D);
			}

			void LoadScene(const String & fileName)
			{
				if (FileExists(fileName))
				{
					scene = new Scene();
					scene->Load(fileName);
					renderer->SetScene(scene.operator ->());
					String solutionFileName = ExtractFilePath(fileName) + ExtractFileNameWithoutEXT(fileName) + L".gxilm";
					if (FileExists(solutionFileName))
					{
						solution = new LightmapSolution(scene.operator ->());
						solution->Load(solutionFileName);
						renderer->LoadLightmapSolution(solution);
					}
					String physFileName = ExtractFilePath(fileName) + ExtractFileNameWithoutEXT(fileName) + L".phy";
					if (FileExists(physFileName))
					{
						physScene = engine->CreatePhysScene();
						physScene->Load(physFileName);
					}
				}
			}

			void matCubeTestUniform(Material * mat, Object * target, const String & uniName, int loc)
			{
				if (uniName == L"cube")
				{
					/*if (solution)
					{
						glEnable(GL_TEXTURE_CUBE_MAP);
						solution->TestCubeMap->Bind();
						Shader::Uniform1iARB(loc, 0);
					}*/
				}
				else
				{
					float mat[9];
					cam.GetInverseRotationMatrix(mat);
					Shader::UniformMatrix3fv(loc,1,GL_TRUE,mat);
			
				}
			}

			void DrawSphere()
			{
				matCubeTest->Bind();
				GLUquadricObj * spObj = gluNewQuadric();
				gluQuadricNormals(spObj,GLU_SMOOTH);
				gluQuadricOrientation(spObj,GLU_OUTSIDE);
				gluSphere(spObj,20,50,50);
				gluDeleteQuadric(spObj);
				Shader::UseProgramObjectARB(0);
				glDisable(GL_TEXTURE_CUBE_MAP);
				glBindTexture(GL_TEXTURE_CUBE_MAP,0);
			}

			int DebugLightmap;
			bool DrawPortals;

			void OnMainLoop(Object * sender, EventArgs e)
			{
				static bool firstFrame = true;
				static LARGE_INTEGER lastTime, curTime, freq;
				QueryPerformanceCounter(&curTime);
				QueryPerformanceFrequency(&freq);
				float dtime = (curTime.QuadPart-lastTime.QuadPart)/(float)(freq.QuadPart);
				fps = 1.0f/dtime;
				if (dtime < 0.0f)
					dtime = 0.1f;
				else if (dtime > 0.5f)
					dtime = 0.5f;
				if (firstFrame)
				{
					dtime = 0.0f;
					firstFrame = false;
				}
				lastTime = curTime;

				HandleCameraKeys(dtime);
				if (physScene)
				{
					physScene->Step(dtime);
					physScene->FetchResult();
					engine->GetControllerManager()->updateControllers();
					physScene->GetCameraPos(cam.pos);
				}
				glClearColor(0.0f,0.3f,0.6f,1.0f);
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

				glPushMatrix();
				cam.Transform();
				
				
				/*vec3 lpos = cam.pos;
				Event<> proc;
				proc.Bind(this, &BspForm::RenderScene);
				cam.pos = scene->Entities[0]->Position+vec3(0.0f,-10.0f,0.0f);
				cubemap->Render(&proc, cam.pos);
				cubemap->Bind();
				glPushMatrix();
				glTranslatef(cam.pos.x, cam.pos.y, cam.pos.z);
				cam.pos = lpos;
				DrawSphere();
				glPopMatrix();
				glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
				*/

				RenderScene();

				glPopMatrix();
				ui->DrawUI();
				if (DrawLightmap)
				{
					ui->BeginUIDrawing();
					glEnable(GL_TEXTURE_2D);
					renderer->DebugDrawLightmap(DebugLightmap);
					ui->EndUIDrawing();
				}
				SwapBuffers(wglGetCurrentDC());
				
			}

			void RenderScene()
			{
				
				int id = -1;
				if (scene)
				{
					scene->SelectLeaf(cam.pos, id);
					renderer->SetNode(id);
					renderer->Render(1.0f/fps);
					if (id != -1 && DrawPortals)
					{
						
						glDisable(GL_DEPTH_TEST);
						renderer->WireFrame = true;
						renderer->Render(1.0f/fps);
						renderer->WireFrame = false;
						Shader::UseProgramObjectARB(0);

						for (int i=0; i<8; i++)
						{
							MultiTexture::ActiveTexture(GL_TEXTURE0 + i);
							glDisable(GL_TEXTURE_2D);
						}
						MultiTexture::ActiveTexture(GL_TEXTURE0);
						glEnable(GL_DEPTH_TEST);
						glColor4ub(127,127,255,255);
						glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
						glEnable(GL_BLEND);
						glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
						renderer->DrawPortal(id);
						glColor4ub(127,127,255,80);
						glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
						renderer->DrawPortal(id);
						glColor4ub(255,0,0,255);
						renderer->DrawLeaf(id);
						glEnable(GL_DEPTH_TEST);
					}
					
					if (id != -1)
					{
						int parent = scene->BSP[id].Parent;
						String txt = String(L"Current Node: ")+String(id) + L", ";
						if (parent != 0xFFFF && scene->BSP[parent].Children[1] == id)
							txt += L"Neg; ";
						else
							txt += L"Normal; ";
						txt += L"Position: ";
						txt += String(cam.pos.x,2) + L", " + String(cam.pos.y,2) + L", " + String(cam.pos.z,2);
						lblNode->SetText(txt+L" FPS: "+String(fps, 2));
					}
				}

				glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
				Shader::UseProgramObjectARB(0);
			}

			void HandleCameraKeys(float dtime)
			{
				//int forward=0, left=0;
				static vec3 speed;
				vec3 force;
				vec3 left;
				left.x=cos(cam.alpha);	left.y=0;	left.z=-sin(cam.alpha);
				if (GetAsyncKeyState(L'W') != 0)
				{
					force += cam.dir;
				}
				else if (GetAsyncKeyState(L'S') != 0)
				{
					force -= cam.dir;
				}
				if (GetAsyncKeyState(L'A') != 0)
				{
					force += left;
				}
				else if (GetAsyncKeyState(L'D') != 0)
				{
					force -= left;
				}
				Normalize(force,force);
				float accelLen = CameraAcceleration * dtime;
				if (speed.GetLength2() < accelLen * accelLen * 16.0f)
				{
					speed = vec3(0.0f,0.0f,0.0f);
				}
				else
				{
					vec3 spdDir;
					Normalize(speed, spdDir);
					speed -= spdDir * accelLen * 4.0f;
				}
				
				speed += force * accelLen * 5.0f;
				float spdLen = speed.GetLength2();
				if (spdLen > CameraMaxSpeed*CameraMaxSpeed)
				{
					Normalize(speed,speed);
					speed *= CameraMaxSpeed;
				}

				float lturn = 0.0f;
				float uturn = 0.0f;
				if (GetAsyncKeyState(VK_LEFT))
				{
					lturn = CameraTurnAngle * dtime;
				}
				if (GetAsyncKeyState(VK_RIGHT))
				{
					lturn = -CameraTurnAngle * dtime;
				}

				if (GetAsyncKeyState(VK_UP))
				{
					uturn = CameraTurnAngle * dtime;
				}
				if (GetAsyncKeyState(VK_DOWN))
				{
					uturn = -CameraTurnAngle * dtime;
				}
				

				//vec3 MoveDir;

				//if (physScene)
				//{
				//	if (forward == 1)
				//		MoveDir = vec3((float)sin(cam.alpha),
				//				   0.0f,
				//				   (float)cos(cam.alpha));
				//	else if (forward == -1)
				//		MoveDir = vec3((float)-sin(cam.alpha),
				//				   0.0f,
				//				   (float)-cos(cam.alpha));
				//}
				//else
				//{
				//	if (forward == 1)
				//		MoveDir = cam.dir;
				//	else if (forward == -1)
				//		MoveDir = -cam.dir;
				//}
				//
				//if (left == 1)
				//{
				//	MoveDir += l;
				//}
				//else if (left == -1)
				//{
				//	MoveDir -= l;
				//}

				//if (MoveDir.GetLength2() > 0.0f)
				//{
				//	Normalize(MoveDir, MoveDir);
				//	if (speed < CameraMinSpeed)
				//		speed = CameraMinSpeed;
				//	else
				//	{
				//		speed += CameraAcceleration*dtime;
				//		if (speed > CameraMaxSpeed)
				//			speed = CameraMaxSpeed;
				//	}				
				//}
				//else
				//{
				//	if (speed > 0.0f)
				//		speed -= CameraAcceleration*10*dtime;
				//	if (speed < 0.0f)
				//		speed = 0.0f;
				//}
				if (physScene)
				{
					if (GetAsyncKeyState(VK_SPACE) != 0)
						physScene->Controller->Jump();
					physScene->Controller->MoveDirection(force, dtime);
				}
				else
				{
					cam.pos += speed*dtime;
				}
				cam.TurnLeft(lturn);
				cam.TurnUp(uturn);

			}

		};
	}
}

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd)
{
	Application::Init(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
	FinalRendering::BspEditor::BspForm * mainForm = new FinalRendering::BspEditor::BspForm();
	Application::SetMainLoopEventHandler(new GxWinGUI::NotifyEvent(mainForm, &FinalRendering::BspEditor::BspForm::OnMainLoop));
	Application::Run(mainForm, true);
	
	Application::Dispose();
	return 1;
}